package com.basker.pisces.domain.event;

import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.core.ResolvableType;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import com.basker.pisces.domain.command.meta.Command;
import com.basker.pisces.domain.entity.meta.Entity;
import com.basker.pisces.domain.event.handler.IDomainEventHanlder;
import com.basker.pisces.domain.event.handler.annotation.DomainEventHandler;

/**
 * 领域事件分发器
 *
 * @author hangwen
 */
@Component
class DomainEventDispatcher {

    @Autowired(required = false)
    private List<IDomainEventHanlder<?>> eventHandlers;

    @EventListener
    public void onCommandExecuted(CommandExecutedDomainEvent event) {
        if (!this.valid()) {
            return;
        }

        List<IDomainEventHanlder<CommandExecutedDomainEvent>> handlers = this.findHandlers(event);
        for (IDomainEventHanlder<CommandExecutedDomainEvent> handler : handlers) {
            handler.handleEvent(event);
        }
    }

    /**
     * 命令过滤
     *
     * @param command
     * @return
     */
    private Predicate<IDomainEventHanlder<?>> commandFilter(Command command) {
        return handler -> {
            DomainEventHandler sourceLimit = this.getSourceLimit(handler);
            if (sourceLimit == null) {
                // 没有限定
                return true;
            }

            String[] limitCommandName = sourceLimit.limitCommandName();
            if (!ObjectUtils.isEmpty(limitCommandName)) {
                boolean allNotMatch = Stream.of(limitCommandName).allMatch(commandNameNotMatch(command));
                if (allNotMatch) {
                    // command不满足所有的limitCommandName
                    return false;
                }
            }

            return true;
        };
    }

    /**
     * command不是指定的limitCommandName
     *
     * @param command
     * @return
     */
    private Predicate<? super String> commandNameNotMatch(Command command) {
        return limitCommandName -> !limitCommandName.equals(command.getName());
    }

    @SuppressWarnings("unchecked")
    private <T extends DomainEvent> Function<IDomainEventHanlder<?>, IDomainEventHanlder<T>> convertMapper() {
        return handler -> (IDomainEventHanlder<T>) handler;
    }

    /**
     * 实体过滤
     *
     * @param entity
     * @return
     */
    private Predicate<IDomainEventHanlder<?>> entityFilter(Entity entity) {
        return handler -> {
            DomainEventHandler sourceLimit = this.getSourceLimit(handler);
            if (sourceLimit == null) {
                // 没有限定
                return true;
            }

            Class<? extends Entity>[] limitEntityType = sourceLimit.limitEntityType();
            if (!ObjectUtils.isEmpty(limitEntityType)) {
                boolean allNotMatch = Stream.of(limitEntityType).allMatch(entityTypeNotMatch(entity));
                if (allNotMatch) {
                    // entity不满足所有的limitEntityType
                    return false;
                }
            }

            return true;
        };
    }

    /**
     * entity不是指定的limitEntityType
     *
     * @param entity
     * @return
     */
    private Predicate<Class<? extends Entity>> entityTypeNotMatch(Entity entity) {
        return limitEntityType -> !limitEntityType.isAssignableFrom(entity.getClass());
    }

    private Predicate<IDomainEventHanlder<?>> eventTypeFilter(Class<? extends DomainEvent> eventType) {
        return handler -> {
            Class<?> handlerType = handler.getClass();
            if (AopUtils.isAopProxy(handler)) {
                handlerType = AopUtils.getTargetClass(handler);
            }

            Class<?> handlerEventType = ResolvableType.forType(handlerType).as(IDomainEventHanlder.class)
                    .resolveGenerics()[0];
            return eventType.equals(handlerEventType);
        };
    }

    private List<IDomainEventHanlder<CommandExecutedDomainEvent>> findHandlers(CommandExecutedDomainEvent event) {
        Class<? extends DomainEvent> eventType = event.getClass();

        Stream<IDomainEventHanlder<CommandExecutedDomainEvent>> stream = this.eventHandlers.stream()
                .filter(eventTypeFilter(eventType)).filter(entityFilter(event.getEntity()))
                .filter(commandFilter(event.getCommand())).map(convertMapper());

        return stream.collect(Collectors.toList());
    }

    private DomainEventHandler getSourceLimit(IDomainEventHanlder<?> handler) {
        return handler.getClass().getAnnotation(DomainEventHandler.class);
    }

    private boolean valid() {
        return !CollectionUtils.isEmpty(eventHandlers);
    }
}
